import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

public class CADUEventProcessor extends DrawObjectEventProcessor
{
	private ArrayList<DrawObject> ClearDrawObjectArrayList(ArrayList<DrawObject> DrawObjectArrayList)
	{
		for (int m = 0; m < DrawObjectArrayList.size(); m++)
			DrawObjectArrayList.set(m, null);
		DrawObjectArrayList = null;
		System.gc();
		return DrawObjectArrayList;
	}

	private ArrayList<DrawObject> CreateCADU(Point Pos, int AxonCount, int DendritesCount, int[] AllowedDelaysInTicks, int LearningNeuronCount)
	{ // returns all DrawObjects the CADU consists of or null in case of an error (currently if out of heap space)

		ArrayList<DrawObject> CADUObjects = new ArrayList<DrawObject>();

		int GridSizex = 50; // TODO: don't hard code
		int GridSizey = 50; // TODO: don't hard code

		int AllowedDelayInTicksMax = 0;

		for (int m = 0; m < AllowedDelaysInTicks.length; m++)
			if (AllowedDelaysInTicks[m] > AllowedDelayInTicksMax)
				AllowedDelayInTicksMax = AllowedDelaysInTicks[m];

		Pos.x += GridSizex * 1; // simple correction to compensate non-optimal calculations in following code
		Pos.y += GridSizey * 3; // simple correction to compensate non-optimal calculations in following code

		int CADUWidth = 0;
		int CADUHeight = 0;

		Rectangle[] InterfaceCommentFieldRectangles = new Rectangle[4];

		for (int i = 0; i < InterfaceCommentFieldRectangles.length; i++)
			InterfaceCommentFieldRectangles[i] = new Rectangle();

		// create Nodes of vertical (axon) pathway...
		// ooo <--
		// |||
		// ooo <--
		// |||
		// ooo <--

		Node[] VerticalNodes = new Node[AxonCount * (AllowedDelayInTicksMax + 1)];

		for (int x = 0; x < AxonCount; x++)
		{
			for (int y = 0; y <= AllowedDelayInTicksMax; y++)
			{
				int NodePosx = Pos.x + x * GridSizex;
				int NodePosy = Pos.y + y * GridSizey;

				VerticalNodes[y * AxonCount + x] = new Node(new Point(NodePosx, NodePosy));

				CADUObjects.add(VerticalNodes[y * AxonCount + x]);

				CADUWidth = Math.max(CADUWidth, NodePosx - Pos.x);
				CADUHeight = Math.max(CADUHeight, NodePosy - Pos.y);

				if (x == 0 && y == 0)
				{
					InterfaceCommentFieldRectangles[1].x = NodePosx - GridSizex;
					InterfaceCommentFieldRectangles[1].y = NodePosy - GridSizey * 2;
				}
				if (x == AxonCount - 1 && y == 0)
				{
					InterfaceCommentFieldRectangles[1].width = NodePosx + GridSizex - InterfaceCommentFieldRectangles[1].x;
					InterfaceCommentFieldRectangles[1].height = GridSizey * 3;
				}
			}
		}

		// connect Nodes top down...
		// ooo
		// ||| <--
		// ooo
		// ||| <--
		// ooo

		for (int x = 0; x < AxonCount; x++)
		{
			for (int y = 1; y <= AllowedDelayInTicksMax; y++)
			{
				Line Line = new Line(VerticalNodes[(y - 1) * AxonCount + x], VerticalNodes[y * AxonCount + x]);

				CADUObjects.add(Line);
			}
		}

		// create LearningNeurons and their "interface" Nodes...

		// LearningNeurons can optionally be used to "filter" output of
		// the normal CADU Neuron array - LearningNeuron only fires
		// if a specific normal Neuron fires.

		int NeuronCount = (int) Math.pow(AllowedDelaysInTicks.length, DendritesCount);

		Neuron[] LearningNeurons = new Neuron[LearningNeuronCount];

		for (int n = 0; n < LearningNeuronCount; n++)
		{
			// create actual LearningNeuron

			int NeuronSizey = 100; // TODO: don't hard code
			int NeuronPosyMax = Pos.y + GridSizey * 2 + (NeuronCount - 1) * (NeuronSizey + GridSizey); // (NeuronCount - 1) is the maximal "n" used when placing normal Neurons (see below)

			int NodePosyMax = Pos.y + AllowedDelayInTicksMax * GridSizey; // position of the bottom-most Nodes from the vertical pathway

			int LearningNeuronSizex = 100; // TODO: don't hard code
			int LearningNeuronPosy = Math.max(NeuronPosyMax, NodePosyMax) + NeuronSizey + GridSizey;

			Neuron LearningNeuron = new Neuron(new Point(Pos.x + n * (LearningNeuronSizex + GridSizex), LearningNeuronPosy));
			LearningNeuron.SetIsLearningNeuron(true);
			LearningNeuron.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

			CADUWidth = Math.max(CADUWidth, (LearningNeuron.Pos.x + LearningNeuron.Size.x) - Pos.x);
			CADUHeight = Math.max(CADUHeight, (LearningNeuron.Pos.y + LearningNeuron.Size.y) - Pos.y);

			NeuronOutputNode LearningNeuronOutputNode = LearningNeuron.AddOutputNode();
			LearningNeuronOutputNode.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

			Neuron.NeuronInputDrawObjects LearningNeuronInputDrawObjects = LearningNeuron.AddInputFieldAndNode();
			LearningNeuronInputDrawObjects.GetField().ToggleIsNegative(); // this is the NEGATIVE input, normal inputs are added below!
			LearningNeuronInputDrawObjects.GetField().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);
			LearningNeuronInputDrawObjects.GetNode().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

			CADUObjects.add(LearningNeuron);
			CADUObjects.add(LearningNeuronOutputNode);
			CADUObjects.add(LearningNeuronInputDrawObjects.GetField()); // add field ALWAYS before node!
			CADUObjects.add(LearningNeuronInputDrawObjects.GetNode());

			LearningNeurons[n] = LearningNeuron;

			// create connection Lines from/to interface Nodes...

			// from interface Node to LearningNeuron's negative input
			int LearningNeuronNegativeInputInterfaceNodex = Pos.x + (AxonCount * GridSizex) + GridSizex + (n * GridSizex);

			Node LearningNeuronNegativeInputInterfaceNode = new Node(new Point(LearningNeuronNegativeInputInterfaceNodex, Pos.y));
			Line LearningNeuronNegativeInputInterfaceLine = new Line(LearningNeuronNegativeInputInterfaceNode, LearningNeuronInputDrawObjects.GetNode());
			LearningNeuronNegativeInputInterfaceLine.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

			CADUObjects.add(LearningNeuronNegativeInputInterfaceNode);
			CADUObjects.add(LearningNeuronNegativeInputInterfaceLine);

			if (n == 0)
			{
				InterfaceCommentFieldRectangles[2].x = LearningNeuronNegativeInputInterfaceNodex - GridSizex;
				InterfaceCommentFieldRectangles[2].y = Pos.y - GridSizey * 2;
			}
			if (n == LearningNeuronCount - 1)
			{
				InterfaceCommentFieldRectangles[2].width = LearningNeuronNegativeInputInterfaceNodex + GridSizex * 3 - InterfaceCommentFieldRectangles[2].x;
				InterfaceCommentFieldRectangles[2].height = GridSizey * 3;
			}

			// from LearningNeuron output to interface Node
			int InterfaceOutputNodex = Pos.x + (AxonCount * GridSizex) + GridSizex + (LearningNeuronCount * GridSizex) + GridSizex * 3 + (n * GridSizex);

			Node LearningNeuronOutputInterfaceNode = new Node(new Point(InterfaceOutputNodex, Pos.y));
			Line LearningNeuronOutputInterfaceLine = new Line(LearningNeuronOutputNode, LearningNeuronOutputInterfaceNode);
			LearningNeuronOutputInterfaceLine.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

			CADUObjects.add(LearningNeuronOutputInterfaceNode);
			CADUObjects.add(LearningNeuronOutputInterfaceLine);

			if (n == 0)
			{
				InterfaceCommentFieldRectangles[3].x = InterfaceOutputNodex - GridSizex;
				InterfaceCommentFieldRectangles[3].y = Pos.y - GridSizey * 2;
			}
			if (n == LearningNeuronCount - 1)
			{
				InterfaceCommentFieldRectangles[3].width = InterfaceOutputNodex + GridSizex * 3 - InterfaceCommentFieldRectangles[3].x;
				InterfaceCommentFieldRectangles[3].height = GridSizey * 3;
			}
		}

		// create normal Neurons and their Line connections...

		int LoopCount = (int) Math.pow(2.0, AxonCount);
		int AxonUsedSetIndex = 0;
		int CheckHeapSizeStep = 100;

		for (int AxonUsedBits = 0; AxonUsedBits < LoopCount; AxonUsedBits++)
		{
			int BitSetCount = 0;
			for (int m = 0; m < AxonCount; m++)
				if ((AxonUsedBits & (1 << m)) != 0) // by checking bits, you can get any on/off combination possible
					BitSetCount++;

			if (BitSetCount != DendritesCount) // every Neuron is connected with one possible combination of vertical pathway Nodes (some Nodes might not be reached, their only task is to delay excitement that's traveling through the vertical pathway)
				continue;

			// determine which vertical pathway "lines" are to be used...
			// ooo
			// |||
			// ooo
			// |||
			// ooo
			// ^ ^
			// | |

			boolean[] AxonUsed = new boolean[AxonCount];
			for (int m = 0; m < AxonCount; m++)
				if ((AxonUsedBits & (1 << m)) != 0)
					AxonUsed[m] = true;
				else
					AxonUsed[m] = false;

			// now create "normal" Neurons...

			Neuron[] Neurons = new Neuron[NeuronCount];

			int NeuronSizex = 100; // TODO: don't hard code
			int NeuronSizey = 100; // TODO: don't hard code

			for (int n = 0; n < NeuronCount; n++)
			{
				if ((n % CheckHeapSizeStep) == 0)
					if (!(Tools.CheckHeapSize()))
						return ClearDrawObjectArrayList(CADUObjects);

				// create normal Neuron
				int NeuronPosx = Pos.x + AxonCount * GridSizex + GridSizex + AxonUsedSetIndex * NeuronSizex * 2;
				int NeuronPosy = Pos.y + GridSizey * 2 + n * (NeuronSizey + GridSizey);

				Neurons[n] = new Neuron(
					new Point(
						NeuronPosx,
						NeuronPosy
					)
				);
				Neurons[n].AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

				NeuronOutputNode NeuronOutputNode = Neurons[n].AddOutputNode();
				NeuronOutputNode.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

				CADUObjects.add(Neurons[n]);
				CADUObjects.add(NeuronOutputNode);

				CADUWidth = Math.max(CADUWidth, (Neurons[n].Pos.x + Neurons[n].Size.x) - Pos.x);
				CADUHeight = Math.max(CADUHeight, (Neurons[n].Pos.y + Neurons[n].Size.y) - Pos.y);

				// connect normal Neuron output with a newly created input of each LearningNeuron
				for (int l = 0; l < LearningNeuronCount; l++)
				{
					Neuron.NeuronInputDrawObjects LearningNeuronInputDrawObjects = LearningNeurons[l].AddInputFieldAndNode();

					LearningNeuronInputDrawObjects.GetField().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);
					LearningNeuronInputDrawObjects.GetNode().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

					Line Line = new Line(NeuronOutputNode, LearningNeuronInputDrawObjects.GetNode());
					Line.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

					CADUObjects.add(LearningNeuronInputDrawObjects.GetField());
					CADUObjects.add(LearningNeuronInputDrawObjects.GetNode());
					CADUObjects.add(Line);

					CADUWidth = Math.max(CADUWidth, (LearningNeurons[l].Pos.x + LearningNeurons[l].Size.x) - Pos.x);
					CADUHeight = Math.max(CADUHeight, (LearningNeurons[l].Pos.y + LearningNeurons[l].Size.y) - Pos.y);
				}
			}

			int[] AxonNodeIndex = new int[AxonCount];

			int ConnectionNeuronIndex = 0;
			boolean ConnectionFinished = false;

			while (!(ConnectionFinished))
			{
				for (int x = 0; x < AxonCount; x++)
				{
					if (AxonUsed[x])
					{
						if ((x % CheckHeapSizeStep) == 0)
							if (!(Tools.CheckHeapSize()))
								return ClearDrawObjectArrayList(CADUObjects);

						Node ConnectionNode = VerticalNodes[AllowedDelaysInTicks[AxonNodeIndex[x]] * AxonCount + x];
						Neuron ConnectionNeuron = Neurons[ConnectionNeuronIndex];

						Neuron.NeuronInputDrawObjects ConnectionNeuronInputFieldAndNode = ConnectionNeuron.AddInputFieldAndNode();
						ConnectionNeuronInputFieldAndNode.GetField().SetStrength((int) Math.ceil(ConnectionNeuron.GetStrengthMax() / (double) DendritesCount));
						ConnectionNeuronInputFieldAndNode.GetField().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);
						ConnectionNeuronInputFieldAndNode.GetNode().AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

						CADUObjects.add(ConnectionNeuronInputFieldAndNode.GetField());
						CADUObjects.add(ConnectionNeuronInputFieldAndNode.GetNode());

						Line ConnectionLine = new Line(ConnectionNode, ConnectionNeuronInputFieldAndNode.GetNode());
						ConnectionLine.AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);

						CADUObjects.add(ConnectionLine);
					}
				}

				// define next combination of Nodes on the vertical pathway...

				// NOTE: on the right-most axon pathway line, we try to connect at the next Node below,
				// if there is none, connect to the top-most again and check the further left
				// vertical pathway line in the same way; only check those vertical pathways lines
				// which belong to the current x-on/off combination...

				for (int m = AxonCount - 1; m >= 0; m--)
				{
					if (AxonUsed[m])
					{
						AxonNodeIndex[m]++;
						if (AxonNodeIndex[m] >= AllowedDelaysInTicks.length)
						{
							AxonNodeIndex[m] = 0;

							boolean AxonUsedBefore = false;
							for (int n = m - 1; n >= 0; n--)
								if (AxonUsed[n])
									AxonUsedBefore = true;

							if (!(AxonUsedBefore))
							{
								ConnectionFinished = true;
								break;
							}
						}
						else
							break;
					}
				}

				ConnectionNeuronIndex++;
			}

			AxonUsedSetIndex++;
		}

		// create a CommentField which serves as a "frame" around the CADU,
		// as a visual help for the user (user can also name the CADU by
		// setting CommentField's TextField text)...

		InterfaceCommentFieldRectangles[0].x = Pos.x - GridSizex * 1;
		InterfaceCommentFieldRectangles[0].y = Pos.y - GridSizey * 3;
		InterfaceCommentFieldRectangles[0].width = CADUWidth + GridSizex * 2;
		InterfaceCommentFieldRectangles[0].height = CADUHeight + GridSizey * 4;

		for (int i = 0; i < (LearningNeuronCount == 0 ? 1 : InterfaceCommentFieldRectangles.length); i++) // if no LearningNeurons, just draw the frame around the complete CADU
		{ // ^mind draw order, so that big CADU's frame Lines do not cover interface frames' TextFields

			CommentField CommentField = new CommentField(
				new Point(InterfaceCommentFieldRectangles[i].x, InterfaceCommentFieldRectangles[i].y),
				new Point(InterfaceCommentFieldRectangles[i].width, InterfaceCommentFieldRectangles[i].height)
			);

			CADUObjects.add(CommentField);

			ArrayList<DrawObject> CommentFieldDrawObjects = CommentField.GenerateChildObjects();

			for (int m = 0; m < CommentFieldDrawObjects.size(); m++)
			{
				if (!(CommentFieldDrawObjects.get(m) instanceof TextField))
					CommentFieldDrawObjects.get(m).AddProperty(DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE);
				else
				{
					// if (i == 0) // user shall name this properly
					// ((TextField) CommentFieldDrawObjects.get(m)).SetText("");
					if (i == 1)
						((TextField) CommentFieldDrawObjects.get(m)).SetText("always if this");
					if (i == 2)
						((TextField) CommentFieldDrawObjects.get(m)).SetText("and once this");
					if (i == 3)
						((TextField) CommentFieldDrawObjects.get(m)).SetText("then always this");
				}
				CADUObjects.add(CommentFieldDrawObjects.get(m));
			}
		}

		// finished, return all created DrawObjects, caller must add them to ObjectStore
		return CADUObjects;
	}

	@Override
	public void TryProcessUserKeyboardAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor, ObjectStorage ObjectStorage, IOProgram IOProgram)
	{
		if (!(KeyboardEventsToBeProcessed(GUIElements)))
			return;

		// ****************************************************************
		// u (create CADU)
		if (KeyEventProcessor.IsKeyCodePressed(KeyEvent.VK_U) &&
			KeyEventProcessor.IsControlPressed())
		{
			String AxonCountString = Tools.ShowInputDialog("ChaoticalANDDetectionUnit: Axon count?\r\n(how many pixels or words exist;\r\nmind that more than 10 could be (very) resource-consuming)");
			if (AxonCountString == null || AxonCountString.length() < 1)
				return;

			int AxonCount = Integer.parseInt(AxonCountString);
			if (AxonCount < 1 || AxonCount > 50)
			{
				Tools.ShowMessageDialog(null, "AxonCount must be between 1 and 50!\r\nCombine CADU units if necessary (output of one is input of another).");
				return;
			}

			String DendritesCountString = Tools.ShowInputDialog("ChaoticalANDDetectionUnit: Dendrites count?\r\n(how many pixels or words required to make Neuron fire)");
			if (DendritesCountString == null || DendritesCountString.length() < 1)
				return;

			int DendritesCount = Integer.parseInt(DendritesCountString);
			if (DendritesCount < 1 || DendritesCount > AxonCount)
			{
				Tools.ShowMessageDialog(null, "DendritesCount must be between 1 and " + AxonCount + " (previously entered AxonCount)!");
				return;
			}

			String SuccessionLengthString = Tools
				.ShowInputDialog("ChaoticalANDDetectionUnit: Succession length?\r\n(regards time, e.g. how many *additional*, consecutive IO program lines involved;\r\nuse 0 to recognize static patterns, like VisualField images)");
			if (SuccessionLengthString == null || SuccessionLengthString.length() < 1)
				return;

			int SuccessionLength = Integer.parseInt(SuccessionLengthString);
			if (SuccessionLength < 0 || SuccessionLength > 5)
			{
				Tools.ShowMessageDialog(null, "SuccessionLength must be between 0 (only detect combinations) and 5!");
				return;
			}

			String LearningNeuronCountString = Tools.ShowInputDialog(
				"ChaoticalANDDetectionUnit: LearningNeuronCount count?\r\n(how many input patterns are to be each associated (AND-ed) with an additional excitement;\r\nserves as a kind of \"filter\": e.g. only those VisualField patterns are of interest\r\nwhich appear at the same time with an excited TextField containing \"this is a flower\")"
			);
			if (LearningNeuronCountString == null || LearningNeuronCountString.length() < 1)
				return;

			int LearningNeuronCount = Integer.parseInt(LearningNeuronCountString);
			if (LearningNeuronCount < 0 || LearningNeuronCount > 50)
			{
				Tools.ShowMessageDialog(null, "LearningNeuronCount must be between 0 and 50!");
				return;
			}

			int[] AllowedDelaysInTicks = new int[SuccessionLength + 1];

			for (int m = 0; m <= SuccessionLength; m++)
				AllowedDelaysInTicks[m] = m * Simulation.TicksPerIOTextAreaLine();

			final String GeneratingMessage = "Generating CADU, please wait...";

			GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText(GeneratingMessage, false, true, true, true);

			ArrayList<DrawObject> Objects = CreateCADU(
				DrawObject.AlignOnGrid(DrawObject.UnZoomPoint(MouseEventProcessor.GetMousePosPoint(GUIElements.GetMainWindow()))),
				AxonCount, DendritesCount, AllowedDelaysInTicks, LearningNeuronCount
			);

			if (Objects != null)
			{
				int NeuronCount = 0;
				for (int m = 0; m < Objects.size(); m++)
				{
					ObjectStorage.AddObjectDirectly(Objects.get(m));
					if (Objects.get(m) instanceof Neuron)
						NeuronCount++;
				}

				ObjectStorage.UnSetPartOfMultiSelectionForAllObjects();

				for (int m = 0; m < Objects.size(); m++)
					Objects.get(m).SetPartOfMultiSelection(true);

				GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText(NeuronCount + " Neurons generated", true, false, false, false);
			}
			else
			{
				Tools.ShowMessageDialog(null, "Error: out of memory!\r\n(CADU would be too large to be created,\r\nwait a moment until Java freed memory and then\r\ntry smaller counts/succession lengths)");
			}

			GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText(GeneratingMessage);
			GUIElements.GetMainWindow().DoRedraw();
		}

		return;
	}

	@Override
	public void TryProcessUserMouseAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor, ObjectStorage ObjectStorage, IOProgram IOProgram)
	{
		if (!(MouseEventsToBeProcessed(GUIElements)))
			return;

		return;
	}
}
